package cn.bugstack.middleware.db.router;

import cn.bugstack.middleware.db.router.annotation.DBRouter;
import cn.bugstack.middleware.db.router.exception.AppException;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Method;

/**
 * 这是 db router 的切面内容
 *
 * @author lihh
 */
@Aspect
@Component("db-router-point")
public class DBRouterJoinPoint {
  
  private final Logger logger = LoggerFactory.getLogger(DBRouterJoinPoint.class);
  
  @Resource
  private DBRouterConfig dbRouterConfig;
  
  // 这里对 注解进行切面
  @Pointcut("@annotation(cn.bugstack.middleware.db.router.annotation.DBRouter)")
  public void aopPoint() {
  }
  
  @Around("aopPoint() && @annotation(dbRouter)")
  public Object doRouter(ProceedingJoinPoint jp, DBRouter dbRouter) throws Throwable {
    // 这是 db key
    String dbKey = dbRouter.key();
    if (StringUtils.isBlank(dbKey)) {
      throw new AppException("annotation DBRouter key is null！");
    }
    
    // 计算路由
    // 此时的 dbKey 可能是 userId, 那么此时的value值 是用户id 值
    String dbKeyAttr = getAttrValue(dbKey, jp.getArgs());
    // 这里的 表个数 * 库个数 eg：4 * 2
    int size = dbRouterConfig.getDbCount() * dbRouterConfig.getTbCount();
    // 扰动函数
    // dbKeyAttr.hashCode() 转换为 32的二进制 进行运算
    // dbKeyAttr.hashCode() 这是高16位 移动到 低16位进行运算
    // dbKeyAttr.hashCode() ^ (dbKeyAttr.hashCode() >>> 16) 无论是高位 还是 低位 都要参数运算
    // 通过 size -1 & xxx 拿到索引的值
    int idx = (size - 1) & (dbKeyAttr.hashCode() ^ (dbKeyAttr.hashCode() >>> 16));
    // 库表索引
    // 如果此时的 idx == 2
    // dbIdx 只是计算的 dix 的索引
    int dbIdx = idx / dbRouterConfig.getTbCount() + 1;
    int tbIdx = idx - dbRouterConfig.getTbCount() * (dbIdx - 1);
    // 设置到 ThreadLocal
    // 从 本地线程进行设置
    DBContextHolder.setDBKey(String.format("%02d", dbIdx));
    DBContextHolder.setTBKey(String.format("%02d", tbIdx));
    logger.info("数据库路由 method：{} dbIdx：{} tbIdx：{}", getMethod(jp).getName(), dbIdx, tbIdx);
    // 返回结果
    try {
      return jp.proceed();
    } finally {
      DBContextHolder.clearDBKey();
      DBContextHolder.clearTBKey();
    }
  }
  
  private Method getMethod(JoinPoint jp) throws NoSuchMethodException {
    Signature sig = jp.getSignature();
    MethodSignature methodSignature = (MethodSignature) sig;
    return jp.getTarget().getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes());
  }
  
  public String getAttrValue(String attr, Object[] args) {
    String filedValue = null;
    for (Object arg : args) {
      try {
        if (StringUtils.isNotBlank(filedValue)) {
          break;
        }
        filedValue = BeanUtils.getProperty(arg, attr);
      } catch (Exception e) {
        logger.error("获取路由属性值失败 attr：{}", attr, e);
      }
    }
    return filedValue;
  }
  
}
